package codigo_gals;

import java.util.ArrayList;
import java.util.List;

import control.CodigoObjeto;
import control.NoLista;
import control.PilhaElementos;
import control.PilhaTipos;
import control.TabelaSimbolos;

public class Semantico implements Constants {

    private TabelaSimbolos tabelaSimbolos = new TabelaSimbolos();

    private List<CodigoObjeto> listaCodigoObj = new ArrayList<CodigoObjeto>();

    private PilhaTipos pilhaTipos = new PilhaTipos();
    private PilhaElementos pilhaElementos = new PilhaElementos();

    private int tipo;
    private int endereco = 1;
    private int contaInst = 1;
    private int contaIdsParaCodigoObjeto = 0;
    private int contaIdsExistentes;
    private int enderecoRecuperadoParaAtrib;

    private String operadorAtrib;
    private String operadorRelacional;
    private String estrutura = "";

    private boolean contextoLoop = false;

    public void executeAction(int action, Token token) throws SemanticError {


	switch (action) {

	// Terminou o programa
	case 1:
	    listaCodigoObj.add(new CodigoObjeto(contaInst, "STP", "0"));
	    break;

	// Determina instrucao com base no tipo para gerar codigo da declaracao
	// de variáveis
	case 2:
	    
	    String instrucao = "";

	    switch (tipo) {

	    case 1:

		instrucao = "ALI";

		for (int i = 0; i < contaIdsExistentes; i++) {
		    tabelaSimbolos.buscaPorEndereco(tabelaSimbolos.getSize() - i).setTipo(1);
		}

		break;

	    case 2:
		
		instrucao = "ALR";
		
		for (int i = 0; i < contaIdsExistentes; i++) {
		    tabelaSimbolos.buscaPorEndereco(tabelaSimbolos.getSize() - i).setTipo(2);
		}
		
		break;

	    case 3:
		
		instrucao = "ALS";
		
		for (int i = 0; i < contaIdsExistentes; i++) {
		    tabelaSimbolos.buscaPorEndereco(tabelaSimbolos.getSize() - i).setTipo(3);
		}
		
		break;

	    case 4:
		instrucao = "ALB";
		
		for (int i = 0; i < contaIdsExistentes; i++) {
		    tabelaSimbolos.buscaPorEndereco(tabelaSimbolos.getSize() - i).setTipo(4);
		}
		
		break;
	    }

	    listaCodigoObj.add(new CodigoObjeto(contaInst, instrucao, String.valueOf(contaIdsParaCodigoObjeto)));
	    contaIdsParaCodigoObjeto = 0;
	    contaInst++;

	    break;

	// Determina tipo de identificador na declaracao de var
	case 3:

	    switch (token.getId()) {
	    // boolean
	    case 9:
		tipo = 4;
		break;
	    // float
	    case 19:
		tipo = 2;
		break;
	    // int
	    case 20:
		tipo = 1;
		break;
	    // string
	    case 26:
		tipo = 3;
		break;
	    }

	    break;

	// Manipulacao de lista de ids (declaracao de variaveis, atribuicao
	// ou com.entrada)
	case 4:

	    if (estrutura.equals("var")) {
		tabelaSimbolos.insere(token.getLexeme(), 0, endereco);
		endereco++;
		contaIdsParaCodigoObjeto++;
		contaIdsExistentes = contaIdsParaCodigoObjeto;
		break;
	    }

	    if (estrutura.equals("entrada")) {
		NoLista noRecuperado = tabelaSimbolos.buscaPorIdentificador(token.getLexeme());

		if (noRecuperado == null) {
		    throw new SemanticError("(" + token.getLexeme() + ") identificador n�o declarado", token.getPosition());
		}

		int enderecoRecuperado = noRecuperado.getEndereco();
		int tipoRecuperado = noRecuperado.getTipo();

		listaCodigoObj.add(new CodigoObjeto(contaInst, "REA", String.valueOf(tipoRecuperado)));
		contaInst++;

		listaCodigoObj.add(new CodigoObjeto(contaInst, "STR", String.valueOf(enderecoRecuperado)));
		contaInst++;

		break;
	    }

	    if (estrutura.equals("atrib")) {
		NoLista no = tabelaSimbolos.buscaPorIdentificador(token.getLexeme());

		if (no == null) {
		    throw new SemanticError("Identificador n�o declarado", token.getPosition());
		}
		enderecoRecuperadoParaAtrib = no.getEndereco();
		break;
	    }

	    // achou um break, tem q t� no contexto de loop e executar primeiro
	    // comando apos acao15, se houver
	case 5:
	    if (!contextoLoop) {
		throw new SemanticError("comando break nao esta no contexto do comando loop", token.getPosition());
	    }

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "JMP", "null"));

	    contextoLoop = false;

	    break;

	// Determina o operador de atribuicao
	case 6:
	    operadorAtrib = token.getLexeme();
	    break;

	// Gera instrucao de atribuicao
	case 7:

	    NoLista no = tabelaSimbolos.buscaPorEndereco(enderecoRecuperadoParaAtrib);

	    int tipoRecuperado = no.getTipo();

	    Integer tipoEmpilhado = pilhaTipos.pop();

	    // pilha nao ta vazia
	    if (tipoEmpilhado != null) {
		// verifica tipos
		if (tipoRecuperado == 1 && tipoEmpilhado != 1) {
		    throw new SemanticError("Identificador do tipo int s� armazena expressao do tipo int!", token.getPosition());
		}
		if (tipoRecuperado == 2 && (tipoEmpilhado != 1 && tipoEmpilhado != 2)) {
		    throw new SemanticError("Identificador do tipo float s� armazena expressao do tipo int ou float!", token.getPosition());
		}
		if (tipoRecuperado == 3 && tipoEmpilhado != 3) {
		    throw new SemanticError("Identificador do tipo string s� armazena expressao do tipo string!", token.getPosition());
		}
		if (tipoRecuperado == 4 && tipoEmpilhado != 4) {
		    throw new SemanticError("Identificador do tipo boolean s� armazena expressao do tipo boolean!", token.getPosition());
		}
	    }

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "STR", String.valueOf(enderecoRecuperadoParaAtrib)));
	    contaInst++;

	    break;

	case 8:
	    // ja fiz na acao 4! :D
	    break;

	case 9:
	    listaCodigoObj.add(new CodigoObjeto(contaInst, "WRT", String.valueOf(0)));
	    contaInst++;
	    break;

	case 10:
	    // selecao
	    break;

	case 11:
	    // selecao
	    break;

	case 12:
	    // selecao
	    break;

	case 13:
	    // selecao
	    break;

	case 14:
	    // implementar pilha de desvios pra poder voltar
	    contextoLoop = true;
	    break;

	case 15:
	    // guardar ultimo comando da lista pra sair do loop da acao 14

	    break;

	case 16:
	    // verificar tipos
	    listaCodigoObj.add(new CodigoObjeto(contaInst, "OR", String.valueOf(0)));
	    contaInst++;
	    break;

	case 17:
	    // verificar tipos
	    listaCodigoObj.add(new CodigoObjeto(contaInst, "AND", String.valueOf(0)));
	    contaInst++;
	    break;

	// boolean true na pilha
	case 18:
	    pilhaTipos.push(4);
	    break;

	// boolean false na pilha
	case 19:
	    pilhaTipos.push(4);
	    break;

	case 20:
	    listaCodigoObj.add(new CodigoObjeto(contaInst, "NOT", String.valueOf(0)));
	    contaInst++;
	    break;

	case 21:
	    operadorRelacional = token.getLexeme();
	    break;

	case 22:

	    Integer tipo1 = pilhaTipos.pop();
	    Integer tipo2 = pilhaTipos.pop();

	    if (tipo1 != null && tipo2 != null) {
		if ((tipo1 == 3 && tipo2 != 3) || (tipo2 == 3 && tipo1 != 3)) {
		    throw new SemanticError("tipos incompativeis para operacao", token.getPosition());
		}
	    }

	    if (operadorRelacional.equals("==")) {
		listaCodigoObj.add(new CodigoObjeto(contaInst, "EQL", String.valueOf(0)));
		contaInst++;
		pilhaTipos.push(4);

	    } else {

		if (operadorRelacional.equals("!=")) {
		    listaCodigoObj.add(new CodigoObjeto(contaInst, "DIF", String.valueOf(0)));
		    contaInst++;
		    pilhaTipos.push(4);

		} else {

		    if (operadorRelacional.equals("<")) {
			listaCodigoObj.add(new CodigoObjeto(contaInst, "SMR", String.valueOf(0)));
			contaInst++;
			pilhaTipos.push(4);

		    } else {

			if (operadorRelacional.equals("<=")) {
			    listaCodigoObj.add(new CodigoObjeto(contaInst, "SME", String.valueOf(0)));
			    contaInst++;
			    pilhaTipos.push(4);

			} else {

			    if (operadorRelacional.equals(">")) {
				listaCodigoObj.add(new CodigoObjeto(contaInst, "BGR", String.valueOf(0)));
				contaInst++;
				pilhaTipos.push(4);

			    } else {

				if (operadorRelacional.equals(">=")) {
				    listaCodigoObj.add(new CodigoObjeto(contaInst, "BGE", String.valueOf(0)));
				    contaInst++;
				    pilhaTipos.push(4);
				}
			    }
			}
		    }
		}

	    }
	    break;

	// <aritmetica> + <termo>
	case 23:
	    NoLista noSoma1 = pilhaElementos.pop();
	    NoLista noSoma2 = pilhaElementos.pop();

	    // verifica tipos
	    if (noSoma1.getTipo() == 3 || noSoma2.getTipo() == 3) {
		throw new SemanticError("Opera��o incompat�vel com tipo string", token.getPosition());
	    }

	    if (noSoma1.getTipo() == 4 || noSoma2.getTipo() == 4) {
		throw new SemanticError("Opera��o incompat�vel com tipo boolean", token.getPosition());
	    }

	    if (noSoma1.getTipo() == 2 || noSoma2.getTipo() == 2) {
		pilhaTipos.push(2);
	    } else {
		pilhaTipos.push(1);
	    }

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "ADD", "0"));

	    contaInst++;

	    break;

	// <aritmetica> - <termo>
	case 24:

	    NoLista noSub1 = pilhaElementos.pop();
	    NoLista noSub2 = pilhaElementos.pop();

	    // verifica tipos
	    if (noSub1.getTipo() == 3 || noSub2.getTipo() == 3) {
		throw new SemanticError("Opera��o incompat�vel com tipo string", token.getPosition());
	    }

	    if (noSub1.getTipo() == 4 || noSub2.getTipo() == 4) {
		throw new SemanticError("Opera��o incompat�vel com tipo boolean", token.getPosition());
	    }

	    if (noSub1.getTipo() == 2 || noSub2.getTipo() == 2) {
		pilhaTipos.push(2);
	    } else {
		pilhaTipos.push(1);
	    }

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "SUB", "0"));

	    contaInst++;

	    break;

	// <termo> * <elemento>
	case 25:

	    NoLista noElemento1 = pilhaElementos.pop();
	    NoLista noElemento2 = pilhaElementos.pop();

	    // verifica tipos
	    if (noElemento1.getTipo() == 3 || noElemento2.getTipo() == 3) {
		throw new SemanticError("Opera��o incompat�vel com tipo string", token.getPosition());
	    }

	    if (noElemento1.getTipo() == 4 || noElemento2.getTipo() == 4) {
		throw new SemanticError("Opera��o incompat�vel com tipo boolean", token.getPosition());
	    }

	    if (noElemento1.getTipo() == 2 || noElemento2.getTipo() == 2) {
		pilhaTipos.push(2);
	    } else {
		pilhaTipos.push(1);
	    }

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "MUL", "0"));

	    contaInst++;

	    break;

	// <termo> / <elemento>
	case 26:

	    NoLista noDiv1 = pilhaElementos.pop();
	    NoLista noDiv2 = pilhaElementos.pop();

	    // verifica tipos
	    if (noDiv1.getTipo() == 3 || noDiv2.getTipo() == 3) {
		throw new SemanticError("Opera��o incompat�vel com tipo string", token.getPosition());
	    }

	    if (noDiv1.getTipo() == 4 || noDiv2.getTipo() == 4) {
		throw new SemanticError("Opera��o incompat�vel com tipo boolean", token.getPosition());
	    }

	    pilhaTipos.push(2);

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "DIV", "0"));

	    contaInst++;

	    break;

	// achou id no <elemento> da <aritmetica>
	case 27:

	    NoLista noElemento = tabelaSimbolos.buscaPorIdentificador(token.getLexeme());
	    if (noElemento == null) {
		throw new SemanticError("(" + token.getLexeme() + ") identificador n�o declarado", token.getPosition());
	    }

	    pilhaElementos.push(noElemento);
	    pilhaTipos.push(noElemento.getTipo());

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "LDV", String.valueOf(noElemento.getEndereco())));

	    contaInst++;

	    break;

	// achou ct_inteira no <elemento>
	case 28:

	    NoLista elementoInt = new NoLista();
	    elementoInt.setTipo(1);
	    elementoInt.setValor(token.getLexeme());

	    pilhaElementos.push(elementoInt);
	    pilhaTipos.push(1);

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "LDI", token.getLexeme()));

	    contaInst++;
	    break;

	// achou ct_real no <elemento>
	case 29:

	    NoLista elementoFloat = new NoLista();
	    elementoFloat.setTipo(2);
	    elementoFloat.setValor(token.getLexeme());

	    pilhaElementos.push(elementoFloat);
	    pilhaTipos.push(2);

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "LDR", token.getLexeme()));

	    contaInst++;
	    break;

	// achou ct_literal no <elemento>
	case 30:

	    NoLista elementoLit = new NoLista();
	    elementoLit.setTipo(3);
	    elementoLit.setValor(token.getLexeme());

	    pilhaElementos.push(elementoLit);
	    pilhaTipos.push(3);

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "LDS", token.getLexeme()));

	    contaInst++;
	    break;

	// achou -<elemento> no <elemento>
	case 31:
	    listaCodigoObj.add(new CodigoObjeto(contaInst, "LDI", String.valueOf(-1)));
	    contaInst++;

	    listaCodigoObj.add(new CodigoObjeto(contaInst, "MUL", String.valueOf(0)));
	    contaInst++;

	    break;

	// declarando variaveis
	case 32:
	    estrutura = "var";
	    break;

	// fazendo comando atribuicao
	case 33:
	    estrutura = "atrib";
	    break;

	// fazendo comando entrada
	case 34:
	    estrutura = "entrada";
	    break;

	default:
	    break;
	}
    }

    public List<CodigoObjeto> getListaCodigoObj() {
	return listaCodigoObj;
    }
}
